{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Distribution Generation Curtailment with OPF\n",
    "This is an introduction on how to use the pandapower optimal power flow for calculation optimal distributed generation curtailment.\n",
    "\n",
    "## Example Network\n",
    "\n",
    "We use the four bus example network from the basic OPF tutorial:\n",
    "\n",
    "<img src=\"pics/example_opf.png\" width=\"50%\">\n",
    "\n",
    "We first create this network in pandapower:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:21:10.764979Z",
     "start_time": "2025-10-20T08:21:10.743917Z"
    }
   },
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "from pandapower.create import (\n",
    "    create_empty_network,\n",
    "    create_ext_grid,\n",
    "    create_bus,\n",
    "    create_transformer,\n",
    "    create_load,\n",
    "    create_lines,\n",
    "    create_gen,\n",
    "    create_poly_cost\n",
    ")\n",
    "from pandapower.run import runopp"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:21:10.953962Z",
     "start_time": "2025-10-20T08:21:10.775059Z"
    }
   },
   "outputs": [],
   "source": [
    "net = create_empty_network()\n",
    "\n",
    "#create buses\n",
    "bus1 = create_bus(net, vn_kv=220., min_vm_pu=1.0, max_vm_pu=1.02)\n",
    "bus2 = create_bus(net, vn_kv=110., min_vm_pu=1.0, max_vm_pu=1.02)\n",
    "bus3 = create_bus(net, vn_kv=110., min_vm_pu=1.0, max_vm_pu=1.02)\n",
    "bus4 = create_bus(net, vn_kv=110., min_vm_pu=1.0, max_vm_pu=1.02)\n",
    "\n",
    "#create 220/110 kV transformer\n",
    "create_transformer(net, bus1, bus2, std_type=\"100 MVA 220/110 kV\", max_loading_percent=100)\n",
    "\n",
    "#create 110 kV lines\n",
    "create_lines(net, [bus2, bus3, bus4], [bus3, bus4, bus2], length_km=[70, 50, 40], std_type='149-AL1/24-ST1A 110.0', max_loading_percent=100)\n",
    "\n",
    "#create loads\n",
    "create_load(net, bus2, p_mw=60, controllable=False)\n",
    "create_load(net, bus3, p_mw=70, controllable=False)\n",
    "create_load(net, bus4, p_mw=10, controllable=False)\n",
    "\n",
    "#create generators\n",
    "eg = create_ext_grid(net, bus1)\n",
    "g0 = create_gen(net, bus3, p_mw=80, min_p_mw=0, max_p_mw=80,  vm_pu=1.01, controllable=True)\n",
    "g1 = create_gen(net, bus4, p_mw=100, min_p_mw=0, max_p_mw=100, vm_pu=1.01, controllable=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:21:11.361821Z",
     "start_time": "2025-10-20T08:21:10.960612Z"
    }
   },
   "outputs": [],
   "source": [
    "create_poly_cost(net, 0, 'gen', cp1_eur_per_mw=-1)\n",
    "create_poly_cost(net, 1, 'gen', cp1_eur_per_mw=-1)\n",
    "create_poly_cost(net, 0, 'ext_grid', cp1_eur_per_mw=0)\n",
    "runopp(net, verbose=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Because of the negative costs, the OPF now maximizes power generation at the generators, which is constrained by their maximum power:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:21:11.377084Z",
     "start_time": "2025-10-20T08:21:11.368030Z"
    }
   },
   "outputs": [],
   "source": [
    "pd.concat([net.res_gen.p_mw, net.gen.min_p_mw, net.gen.max_p_mw], axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "While gen 1 is operating at the limit, gen 0 is below the maximum output. Apparently the generator can not reach its maximum output without violating at least one power flow constraint. Let's check on the constraints.\n",
    "\n",
    "The line and transformer constraints are not reached:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:21:11.453841Z",
     "start_time": "2025-10-20T08:21:11.443757Z"
    },
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "pd.concat([net.line.max_loading_percent, net.res_line.loading_percent], axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:21:11.736648Z",
     "start_time": "2025-10-20T08:21:11.708563Z"
    }
   },
   "outputs": [],
   "source": [
    "pd.concat([net.trafo.max_loading_percent, net.res_trafo.loading_percent], axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But the voltage constraints are:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:21:11.923321Z",
     "start_time": "2025-10-20T08:21:11.898335Z"
    }
   },
   "outputs": [],
   "source": [
    "pd.concat([net.res_bus.vm_pu, net.bus.min_vm_pu, net.bus.max_vm_pu], axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Obviously the voltage profile was the limiting factor for the generator feed-in. If we relax this constraint a little bit:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:21:13.461356Z",
     "start_time": "2025-10-20T08:21:12.216441Z"
    }
   },
   "outputs": [],
   "source": [
    "net.bus[\"max_vm_pu\"] = 1.05\n",
    "runopp(net)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We see an increased feed-in of the generators:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:21:13.493050Z",
     "start_time": "2025-10-20T08:21:13.469Z"
    }
   },
   "outputs": [],
   "source": [
    "pd.concat([net.res_gen.p_mw, net.gen.min_p_mw, net.gen.max_p_mw], axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:21:13.618057Z",
     "start_time": "2025-10-20T08:21:13.598622Z"
    }
   },
   "outputs": [],
   "source": [
    "net.res_bus"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
